﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;


namespace DailyFx.Common
{
	/// <summary>
	///		 Helper, for accessing and creating typed property objects dynamically.
	/// </summary>
	public static class PropertyExtension
	{
		/// <summary>
		///		Gets the field.
		/// </summary>
		public static object GetField (object instance, string fieldName)
		{
			var type = instance.GetType();
			var key = (type.FullName + "_" + fieldName).Replace(".", "_");

			var fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public);
			if (fieldInfo == null)
				throw new ArgumentException("There is no public accessible "
											+ fieldName + " field found in " + type.FullName);

			return GetField( instance, fieldInfo);
		}


		/// <summary>
		///		Gets the field.
		/// </summary>
		public static object GetField ( object instance, FieldInfo fieldInfo )
		{

			if (fieldInfo == null)
				throw new ArgumentException("field info argument is null");

			var type = instance.GetType();
			var key = (type.FullName + "_" + fieldInfo.Name).Replace(".", "_");

			var getter = new DynamicMethod("__get_field_" + key, fieldInfo.FieldType, new[] { type },
											typeof(PropertyExtension), true);

			var getterIL = getter.GetILGenerator();
			//{
				getterIL.Emit(OpCodes.Ldarg_0);
				getterIL.Emit(OpCodes.Ldfld, fieldInfo);
				getterIL.Emit(OpCodes.Ret);
			//}

			return getter.Invoke(null, new[] { instance });
		}


		/// <summary>
		///		Sets the field.
		/// </summary>
		public static void SetField( object instance, string fieldName, object value)
		{

			var type = instance.GetType();
			var key = (type.FullName + "_" + fieldName).Replace(".", "_");

			var fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public);
			if (fieldInfo == null)
				throw new ArgumentException("There is no public accessible "
											+ fieldName + " field found in " + type.FullName);

			var setter = new DynamicMethod("__set_field_" + key, null, new[] { type, fieldInfo.FieldType },
											typeof(PropertyExtension), true);

			var setterIL = setter.GetILGenerator();
			// {
				setterIL.Emit(OpCodes.Ldarg_0);
				setterIL.Emit(OpCodes.Ldarg_1);
				setterIL.Emit(OpCodes.Stfld, fieldInfo);
				setterIL.Emit(OpCodes.Ret);
			// }
			
			setter.Invoke( null, new[] { instance, value });
		}

		/// <summary>
		/// Gets the property.
		/// </summary>
		/// <param name="instance">The instance.</param>
		/// <param name="propertyName">Name of the property.</param>
		/// <returns></returns>
		public static object GetProperty( object instance, string propertyName)
		{

			var type = instance.GetType();
			var key = (type.FullName + "_" + propertyName).Replace(".", "_");

			var propertyInfo = type.GetProperty(propertyName, 
									BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

			if (propertyInfo == null)
				throw new ArgumentException("There is no publicaccessible "
											+ propertyName + " property found in " + type.FullName);

			if (!propertyInfo.CanRead)
				throw new ArgumentException("The property " + propertyName
											+ " has no public accessible getter.");

			var getter = new DynamicMethod("__get_property_" + key, propertyInfo.PropertyType, new[] { type },
											typeof(PropertyExtension), true);

			var getterIL = getter.GetILGenerator();
			//{ 
				getterIL.Emit(OpCodes.Ldarg_0);
				getterIL.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod());
				getterIL.Emit(OpCodes.Ret);
			//}
			
			return getter.Invoke( null, new[] { instance });
		}

		/// <summary>
		///		Sets the property.
		/// </summary>
		public static void SetProperty( object instance, string propertyName, object value)
		{
			var type = instance.GetType();
			var key = (type.FullName + "_" + propertyName).Replace(".", "_");

			var propertyInfo = type.GetProperty(propertyName,
									BindingFlags.Instance | BindingFlags.Public);

			if (propertyInfo == null)
				throw new ArgumentException("There is no public accessible " +
											propertyName + " property found in " + type.FullName);

			if (!propertyInfo.CanWrite)
				throw new ArgumentException("The property " + propertyName +
											" has no publicly accessible setter.");

			var setter = new DynamicMethod("__set_property_" + key, null, new[] { type, propertyInfo.PropertyType },
											typeof(PropertyExtension), true);

			var setterIL = setter.GetILGenerator();
			//{ 
				setterIL.Emit(OpCodes.Ldarg_0);
				setterIL.Emit(OpCodes.Ldarg_1);
				setterIL.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
				setterIL.Emit(OpCodes.Ret);
			//}
			
			setter.Invoke( null, new[] { instance, value });
		}

		/// <summary>
		///		Gets the property delegate.
		/// </summary>
		public static Func<T, Result> FetchProperty<T, Result>(string propertyName)
		{
			Type type = typeof(T);
			var key = (type.FullName + "_" + propertyName).Replace(".", "_");
			var propertyInfo = type.GetProperty(propertyName,
									BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

			if (propertyInfo == null)
				throw new ArgumentException("There is no public accessible " +
											propertyName + " property found in " + type.FullName);

			if (!propertyInfo.CanRead)
				throw new ArgumentException("The property " + propertyName +
											" has no publicly accessible getter.");

			var getterMethod = new DynamicMethod("__get_property_" + key, propertyInfo.PropertyType, new[] { type },
													typeof(PropertyExtension), true);

			var getterIL = getterMethod.GetILGenerator();
			// { 
				getterIL.Emit(OpCodes.Ldarg_0);
				getterIL.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod());
				getterIL.Emit(OpCodes.Ret);
			// }

			return (Func<T, Result>)getterMethod.CreateDelegate( typeof(Func<T, Result>));
		}

		/// <summary>
		///		Sets the property delegate.
		/// </summary>
		public static Action<T> SetProperty<T>( string propertyName)
		{
			Type type = typeof(T);
			var key = (type.FullName + "_" + propertyName).Replace(".", "_");

			var propertyInfo = type.GetProperty(propertyName,
												BindingFlags.Instance | BindingFlags.Public);
			if (propertyInfo == null)
				throw new ArgumentException("There is no public accessible " +
											propertyName + " property found in " + type.FullName);

			if (!propertyInfo.CanWrite)
				throw new ArgumentException("The property " + propertyName +
											" has no publicly accessible setter.");

			var setterMethod = new DynamicMethod("__set_property_" + key, null, new[] { type, propertyInfo.PropertyType },
													typeof(PropertyExtension), true);

			var setterIL = setterMethod.GetILGenerator();
			//{
				setterIL.Emit(OpCodes.Ldarg_0);
				setterIL.Emit(OpCodes.Ldarg_1);
				setterIL.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
				setterIL.Emit(OpCodes.Ret);
			//}
			
			return (Action<T>)setterMethod.CreateDelegate( typeof(Action<T>));
		}


		/// <summary>
		///		Creates the instance.
		/// </summary>
		public static object CreateInstance(Type type, params object[] parameters)
		{
			Type[] paramTypes;
			if (parameters == null)
			{
				paramTypes = new Type[0];
			}
			else
			{
				paramTypes = new Type[ parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					paramTypes[i] = parameters[i].GetType();
				}
			}

			string paramKeys = (paramTypes == null || paramTypes.Length == 0) 
								? string.Empty : string.Concat((IEnumerable<Type>)paramTypes);

			var key = (type.FullName + "_" + paramKeys).Replace(".", "_");

			DynamicMethod method = new DynamicMethod(key, typeof(object), new Type[] { typeof(object[]) }, 
													typeof(PropertyExtension).Module, true);
			ILGenerator il = method.GetILGenerator();
			ConstructorInfo ctor = type.GetConstructor(paramTypes);
			if (ctor == null)
			{
				// The type is stupid struct which does not got constructors
				return null; 
			}

			il.Emit(OpCodes.Nop);
			for (int i = 0; i < paramTypes.Length; i++)
			{
				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldc_I4, i);
				il.Emit(OpCodes.Ldelem_Ref);
				if (paramTypes[i].IsValueType)
				{
					il.Emit(OpCodes.Unbox_Any, paramTypes[i]);
				}
				else
				{
					il.Emit(OpCodes.Castclass, paramTypes[i]);
				}
			}
			il.Emit(OpCodes.Newobj, ctor);
			il.Emit(OpCodes.Ret);


			if ( method != null)
			{
				return method.Invoke( null, new object[] { parameters });
			}
			else // This type is stupid struct that does not got constructors.
			{
				return Activator.CreateInstance( type);
			}
		}

		/// <summary>
		///		Creates the instance.
		/// </summary>
		public static T CreateInstance<T>( params object[] parameters)
		{
			Type type = typeof(T);
			return (T)CreateInstance(type, parameters);
		}


	}
}
